package cn.virens.common.utils.objcet;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.virens.common.exception.APIException;
import cn.virens.common.utils.Assert;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class ObjectUtil {
    /**
     * 获取任意对象的hashcode值
     *
     * @param o 对象值
     * @return hashcode值
     */
    public static int hash(Object o) {
        return o != null ? o.hashCode() : 0;
    }

    /**
     * 调用任意对象的toString方法
     *
     * @param o 对象值
     * @return 字符串
     */
    public static String toString(Object o) {
        return o != null ? o.toString() : "";
    }

    /**
     * 根据hashcode判断2个对象是否相同
     *
     * @param a 对象1
     * @param b 对象2
     * @return 是否相同
     */
    public static boolean equals(Object a, Object b) {
        return a != null && hash(a) == hash(b);
    }

    /**
     * 判断目标对象是否是实现与对应类
     *
     * @param object 目标对象
     * @param clazz  目标类型
     * @return 是否
     */
    public static boolean isAssignableFrom(Object object, Class<?> clazz) {
        return object != null && clazz.isAssignableFrom(object.getClass());
    }

    /**
     * 强制转换对象到目标类型
     *
     * @param <T>    返回类型
     * @param object 目标对象
     * @param clazz  目标类型
     * @return 返回值
     */
    @SuppressWarnings("unchecked")
    public static <T> T to(Object object, Class<T> clazz) {
        if (isAssignableFrom(object, clazz)) {
            return (T) object;
        } else {
            return null;
        }
    }

    /**
     * 将任意对象转为Map
     *
     * @param obj 对象值
     * @param err null错误提示
     * @return 返回Map
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> toMap(Object obj, String err) {
        if (Assert.isNull(obj, err) instanceof Map<?, ?>) {
            return (Map<String, Object>) obj;//
        }

        Map<String, Object> answer = new HashMap<>();

        for (Field field : getFields1(obj.getClass())) {
            String name = ReflectUtil.getFieldName(field);
            Object value = get(obj, field.getName());

            // 判断字段名 & 字段值都 不为空
            if (name != null && value != null) {
                answer.put(name, value);
            }
        }

        return answer;
    }

    /**
     * 将Map转为指定对象
     *
     * @param <T>   对象类型
     * @param map   来源MAP
     * @param clazz 对象CLASS
     * @return 返回构造的对象
     */
    public static <T> T formMap(Map<?, ?> map, Class<T> clazz) {
        T answer = ReflectUtil.newInstance(clazz);

        for (Field field : getFields1(clazz)) {
            fill(map, answer, field);
        }

        return answer;
    }

    /**
     * 将指定字段从Map中取出并填充到dest对象中
     *
     * @param src  来源MAP
     * @param dest 目标对象
     * @param f    指定字段
     * @throws APIException 异常
     */
    private static void fill(Map<?, ?> src, Object dest, Field f) throws APIException {
        Object originalValue = mapGet(src, ReflectUtil.getFieldName(f));
        Object v = Convert.convertQuietly(f.getType(), originalValue);

        if (v != null && dest != null) {
            set(dest, f.getName(), v);
        }
    }

    /***
     * 从Map中获取值(尝试使用驼峰获取)
     *
     * @param  map          map对象
     * @param  key          键
     * @return 值
     * @throws APIException 异常
     */
    private static Object mapGet(Map<?, ?> map, String key) throws APIException {
        Object mapValue = map != null && key != null ? map.get(key) : null;

        if (mapValue == null && map != null && key != null) {
            return map.get(StrUtil.toUnderlineCase(key));
        } else {
            return mapValue;
        }

    }

    /**
     * 调用任意对象指定字段的get方法
     *
     * @param obj       对象
     * @param fieldName 字段名
     * @return 返回值
     */
    public static Object get(Object obj, String fieldName) {
        return get(obj.getClass(), obj, fieldName);
    }

    /**
     * 调用任意对象指定字段的get方法
     *
     * @param clazz     对象class
     * @param obj       对象
     * @param fieldName 字段名
     * @return 返回值
     */
    public static Object get(Class<?> clazz, Object obj, String fieldName) {
        try {
            // 获取对应字段的属性描述器
            PropertyDescriptor descriptor = descriptor(clazz, fieldName);
            if (descriptor == null) return null;

            // 获取对应的get方法 & 调用对应的get方法
            Method method = descriptor.getReadMethod();
            if (obj != null && method != null) {
                return method.invoke(obj);//
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 调用任意对象指定字段的set方法
     *
     * @param obj       对象
     * @param fieldName 字段名
     * @param value     目标值
     * @return 返回值
     */
    public static Object set(Object obj, String fieldName, Object value) {
        return set(obj.getClass(), obj, fieldName, value);
    }

    /**
     * 调用任意对象指定字段的set方法
     *
     * @param clazz     对象class
     * @param obj       对象
     * @param fieldName 字段名
     * @param value     目标值
     * @return 返回值
     */
    public static Object set(Class<?> clazz, Object obj, String fieldName, Object value) {
        try {
            // 获取对应字段的属性描述器
            PropertyDescriptor descriptor = descriptor(clazz, fieldName);
            if (descriptor == null) return null;

            // 获取对应的set方法 & 调用对应的set方法
            Method method = descriptor.getWriteMethod();
            if (obj != null && method != null) {
                return method.invoke(obj, value);//
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IntrospectionException e) {
            e.printStackTrace();
        }

        return null;
    }

    /**
     * 克隆参数到子类
     *
     * @param <T>   父类
     * @param <R>   子类
     * @param obj   父类对象
     * @param clazz 子类class
     * @return 子类对象
     */
    public static <T, R extends T> R clone(T obj, Class<R> clazz) {
        R answer = ReflectUtil.newInstance(clazz);

        for (Field field : getFields0(obj.getClass())) {
            fill(obj, answer, field.getName());
        }

        return answer;
    }

    /**
     * 将一个对象的字段值克隆到另一个
     *
     * @param <T>  父类
     * @param <R>  子类
     * @param src  父类对象
     * @param dest 子类对象
     * @return 子类对象
     * @throws APIException 一般异常
     */
    public static <T, R extends T> R clone(T src, R dest) throws APIException {
        for (Field field : ReflectUtil.getFields(src.getClass())) {
            fill(src, dest, field.getName());
        }

        return dest;
    }

    /**
     * 获取dest值并填充到src中
     *
     * @param src   来源值
     * @param dest  目标值
     * @param field 字段
     * @return 未知
     */
    private static Object fill(Object src, Object dest, String field) {
        return set(dest, field, get(src, field));
    }

    /**
     * 获取指定class的有效字段(非静态)
     *
     * @param clazz 类
     * @return 字段列表
     * @throws APIException 异常
     */
    private static Field[] getFields1(Class<?> clazz) throws APIException {
        return ArrayUtil.filter(getFields0(clazz), field -> {
            return (field.getModifiers() & 0x08) == 0;
        });
    }

    /**
     * 获取指定class的全部字段
     *
     * @param clazz 类
     * @return 字段列表
     * @throws APIException 异常
     */
    private static Field[] getFields0(Class<?> clazz) throws APIException {
        return ReflectUtil.getFieldsDirectly(clazz, true);
    }

    private static PropertyDescriptor descriptor(Class<?> clazz, String field) throws IntrospectionException {
        return clazz != null && field != null ? new PropertyDescriptor(field, clazz) : null;
    }
}
